package org.hepeng.workx.mybatis.binding;

import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.binding.MapperProxyFactory;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.hepeng.workx.extension.DefaultXImpl;
import org.hepeng.workx.extension.XLoader;
import org.hepeng.workx.mybatis.MybatisConstant;
import org.hepeng.workx.mybatis.builder.annotation.MapperAnnotationBuilderParseObjectProcessor;
import org.hepeng.workx.util.ObjectProcessor;
import org.joor.Reflect;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author he peng
 */

@DefaultXImpl
public class MapperRegistryX extends MapperRegistry {

    protected Configuration config;
    protected ObjectProcessor<MapperRegistryGetMapperObjectProcessor.GetMapperPreProcessObject, MapperRegistryGetMapperObjectProcessor.GetMapperPostProcessObject> objectProcessor;

    public MapperRegistryX(Configuration config , ObjectProcessor objectProcessor) {
        super(config);
        this.config = config;
        this.objectProcessor = objectProcessor;
    }

    @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            Map<Class<?>, MapperProxyFactory<?>> knownMappers = Reflect.on(this).get("knownMappers");
            try {
                knownMappers.put(type, new MapperProxyFactory<T>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.

                List<ObjectProcessor<MapperAnnotationBuilderParseObjectProcessor.ParseProcessObject,
                        MapperAnnotationBuilderParseObjectProcessor.ParseProcessObject>> objectProcessors = new ArrayList<>();
                objectProcessors.add(new MapperAnnotationBuilderParseObjectProcessor());

                List<Class<?>> constructorArgTypes = new ArrayList<>();
                constructorArgTypes.add(Configuration.class);
                constructorArgTypes.add(Class.class);
                constructorArgTypes.add(List.class);

                List<Object> constructorArgs = new ArrayList<>();
                constructorArgs.add(config);
                constructorArgs.add(type);
                constructorArgs.add(objectProcessors);

                MapperAnnotationBuilder parser = XLoader.getXLoader(
                        MapperAnnotationBuilder.class , MybatisConstant.MYBATIS_X_POINT_DIRECTORY)
                        .getX(constructorArgTypes , constructorArgs);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

    @Override
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

        MapperRegistryGetMapperObjectProcessor.GetMapperPreProcessObject preProcessObject =
                MapperRegistryGetMapperObjectProcessor.GetMapperPreProcessObject
                .builder()
                .configuration(this.config)
                .sqlSession(sqlSession)
                .type(type)
                .build();
        preProcessObject = objectProcessor.preProcess(preProcessObject);
        type = (Class<T>) preProcessObject.getType();
        sqlSession = preProcessObject.getSqlSession();
        T mapper = super.getMapper(type, sqlSession);

        List<Object> postProcessArgs = new ArrayList<>();
        postProcessArgs.add(type);
        postProcessArgs.add(sqlSession);
        postProcessArgs.add(mapper);

        MapperRegistryGetMapperObjectProcessor.GetMapperPostProcessObject postProcessObject =
                MapperRegistryGetMapperObjectProcessor.GetMapperPostProcessObject
                .builder().sqlSession(sqlSession).type(type).mapper(mapper).build();
        postProcessObject = objectProcessor.postProcess(postProcessObject);
        return (T) postProcessObject.getMapper();
    }

}
